#ifndef __TYPE_CHECK_DISPATCHERS_H__
#define __TYPE_CHECK_DISPATCHERS_H__

#include "ParseBaseNodes.h"

#include <map>
#include <set>
#include <string>
#include <sstream>
#include <stdint.h>
#include "ParseNodes.h"

#define TYPE TypeMetaReference::TypeCategory
#define OP ITypeCheckDispatcher::TypeOp

/* BASE STRUCTURES TO DERIVE FROM */
struct ITypeCheckDispatcher {
	enum TypeOp { 
		ConditionalOr, ConditionalAnd, InclusiveOr, ExclusiveOr, And, 
		EqualTo, NotEqualTo, 
		LessThan, GreaterThan, LessThanEqualTo, GreaterThanEqualTo, InstanceOf,
		Subtraction, Addition, 
		Multiplication, Division, Modulus, 
		Casting, 
		Assignment, 
		Negation,
		Complement};

	virtual TypeMetaReference op(const TypeMetaReference &, const TypeMetaReference &) = 0;

	static TypeMetaReference ResolveType(OP, const TypeMetaReference &, const TypeMetaReference &);

	bool IsSubclassOf(ClassDeclarationNode *c1, ClassDeclarationNode *c2) {
		ClassDeclarationNode *p = c1->super;
		for (; p && p != c2; p = p->super);
		return p != 0x0;
	}
};

template <OP, int, int>
struct TypeCheckDispatcher : ITypeCheckDispatcher {
	TypeMetaReference TypeMetaop(const TypeMetaReference &, const TypeMetaReference &) { return TypeMetaReference(); }
};

/* BOOLEAN OPERATIONS */
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::ConditionalOr, TypeMeta::Boolean, TypeMeta::Boolean> : ITypeCheckDispatcher{																				
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		BooleanLiteralNode *l1 = t1.boolean_literal, *l2 = t2.boolean_literal, *result = 0x0;
		if (l1 && l2) {
			result = new BooleanLiteralNode();
			result->value = l1->value || l2->value;
		}

		return TypeMetaReference(TypeMeta::Boolean, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::ConditionalAnd, TypeMeta::Boolean, TypeMeta::Boolean> : ITypeCheckDispatcher{																			
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		BooleanLiteralNode *l1 = t1.boolean_literal, *l2 = t2.boolean_literal, *result = 0x0;
		if (l1 && l2) {
			result = new BooleanLiteralNode();
			result->value = l1->value && l2->value;
		}

		return TypeMetaReference(TypeMeta::Boolean, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::InclusiveOr, TypeMeta::Boolean, TypeMeta::Boolean> : ITypeCheckDispatcher{																				
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		return TypeCheckDispatcher<ITypeCheckDispatcher::ConditionalOr, TypeMeta::Boolean, TypeMeta::Boolean>().op(t1, t2);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::ExclusiveOr, TypeMeta::Boolean, TypeMeta::Boolean> : ITypeCheckDispatcher{																				
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		BooleanLiteralNode *l1 = t1.boolean_literal, *l2 = t2.boolean_literal, *result = 0x0;
		if (l1 && l2) {
			result = new BooleanLiteralNode();
			result->value = l1->value ^ l2->value;
		}

		return TypeMetaReference(TypeMeta::Boolean, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::And, TypeMeta::Boolean, TypeMeta::Boolean> : ITypeCheckDispatcher{																						
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		return TypeCheckDispatcher<ITypeCheckDispatcher::ConditionalAnd, TypeMeta::Boolean, TypeMeta::Boolean>().op(t1, t2);
	}
};

/* EQUALITY */
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::EqualTo, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int> : ITypeCheckDispatcher{			
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		IntegerLiteralNode *l1 = t1.integer_literal, *l2 = t2.integer_literal;
		BooleanLiteralNode *result = 0x0;
		if (l1 && l2) {
			result = new BooleanLiteralNode();
			result->value = l1->value == l2->value;
		}
		return TypeMetaReference(TypeMeta::Boolean, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::EqualTo, TypeMeta::Boolean, TypeMeta::Boolean> : ITypeCheckDispatcher{																					
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		BooleanLiteralNode *l1 = t1.boolean_literal, *l2 = t2.boolean_literal, *result = 0x0;
		if (l1 && l2) {
			result = new BooleanLiteralNode();
			result->value = l1->value == l2->value;
		}
		return TypeMetaReference(TypeMeta::Boolean, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::EqualTo, TypeMeta::Reference | TypeMeta::String, TypeMeta::Null> : ITypeCheckDispatcher{
	TypeMetaReference op(const TypeMetaReference &, const TypeMetaReference &) {
		return TypeMetaReference(TypeMeta::Boolean, 0x0, 0x0);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::EqualTo, TypeMeta::Null, TypeMeta::Reference | TypeMeta::String> : ITypeCheckDispatcher{
	TypeMetaReference op(const TypeMetaReference &, const TypeMetaReference &) {
		return TypeMetaReference(TypeMeta::Boolean, 0x0, 0x0);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::EqualTo, TypeMeta::Null, TypeMeta::Null> : ITypeCheckDispatcher{																							
	TypeMetaReference op(const TypeMetaReference &, const TypeMetaReference &) {
		BooleanLiteralNode *result = new BooleanLiteralNode();
		result->value = true;
		return TypeMetaReference(TypeMeta::Boolean, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::EqualTo, TypeMeta::Reference | TypeMeta::String, TypeMeta::Reference | TypeMeta::String> : ITypeCheckDispatcher{													
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		ClassDeclarationNode *c1 = t1.reference, *c2 = t2.reference;

		if (c1 == c2 || IsSubclassOf(c1, c2) || IsSubclassOf(c2, c1)) return TypeMetaReference(TypeMeta::Boolean, 0x0, 0x0);

		return TypeMetaReference();
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::NotEqualTo, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int> : ITypeCheckDispatcher{		
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		IntegerLiteralNode *l1 = t1.integer_literal, *l2 = t2.integer_literal;
		BooleanLiteralNode *result = 0x0;
		if (l1 && l2) {
			result = new BooleanLiteralNode();
			result->value = l1->value != l2->value;
		}
		return TypeMetaReference(TypeMeta::Boolean, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::NotEqualTo, TypeMeta::Boolean, TypeMeta::Boolean> : ITypeCheckDispatcher{																				
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		BooleanLiteralNode *l1 = t1.boolean_literal, *l2 = t2.boolean_literal, *result = 0x0;
		if (l1 && l2) {
			result = new BooleanLiteralNode();
			result->value = l1->value != l2->value;
		}
		return TypeMetaReference(TypeMeta::Boolean, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::NotEqualTo, TypeMeta::Reference | TypeMeta::String, TypeMeta::Null> : ITypeCheckDispatcher{
	TypeMetaReference op(const TypeMetaReference &, const TypeMetaReference &) {
		return TypeMetaReference(TypeMeta::Boolean, 0x0, 0x0);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::NotEqualTo, TypeMeta::Null, TypeMeta::Reference | TypeMeta::String> : ITypeCheckDispatcher{
	TypeMetaReference op(const TypeMetaReference &, const TypeMetaReference &) {
		return TypeMetaReference(TypeMeta::Boolean, 0x0, 0x0);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::NotEqualTo, TypeMeta::Null, TypeMeta::Null> : ITypeCheckDispatcher{																						
	TypeMetaReference op(const TypeMetaReference &, const TypeMetaReference &) {
		BooleanLiteralNode *result = new BooleanLiteralNode();
		result->value = false;
		return TypeMetaReference(TypeMeta::Boolean, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::NotEqualTo, TypeMeta::Reference | TypeMeta::String, TypeMeta::Reference | TypeMeta::String> : ITypeCheckDispatcher{												
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		ClassDeclarationNode *c1 = t1.reference, *c2 = t2.reference;

		if (c1 == c2 || IsSubclassOf(c1, c2) || IsSubclassOf(c2, c1)) return TypeMetaReference(TypeMeta::Boolean, 0x0, 0x0);

		return TypeMetaReference();
	}
};

/* RELATIONAL */
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::LessThan, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int> : ITypeCheckDispatcher{			
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		IntegerLiteralNode *l1 = t1.integer_literal, *l2 = t2.integer_literal;
		BooleanLiteralNode *result = 0x0;
		if (l1 && l2) {
			result = new BooleanLiteralNode();
			result->value = l1->value < l2->value;
		}
		return TypeMetaReference(TypeMeta::Boolean, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::GreaterThan, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int> : ITypeCheckDispatcher{		
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		IntegerLiteralNode *l1 = t1.integer_literal, *l2 = t2.integer_literal;
		BooleanLiteralNode *result = 0x0;
		if (l1 && l2) {
			result = new BooleanLiteralNode();
			result->value = l1->value > l2->value;
		}
		return TypeMetaReference(TypeMeta::Boolean, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::LessThanEqualTo, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int> : ITypeCheckDispatcher{	
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		IntegerLiteralNode *l1 = t1.integer_literal, *l2 = t2.integer_literal;
		BooleanLiteralNode *result = 0x0;
		if (l1 && l2) {
			result = new BooleanLiteralNode();
			result->value = l1->value <= l2->value;
		}
		return TypeMetaReference(TypeMeta::Boolean, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::GreaterThanEqualTo, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int> : ITypeCheckDispatcher{
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		IntegerLiteralNode *l1 = t1.integer_literal, *l2 = t2.integer_literal;
		BooleanLiteralNode *result = 0x0;
		if (l1 && l2) {
			result = new BooleanLiteralNode();
			result->value = l1->value >= l2->value;
		}
		return TypeMetaReference(TypeMeta::Boolean, 0x0, result);
	}
};
/* IMPLICIT STRING CONCATENATION */
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Addition, TypeMeta::String, TypeMeta::String> : ITypeCheckDispatcher{																					
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		StringLiteralNode *l1 = t1.string_literal, *l2 = t2.string_literal, *result = 0x0;
		if (l1 && l2) {
			std::stringstream ss;
			ss << l1->value << l2->value;

			result = new StringLiteralNode();
			std::getline(ss, result->value);
		}
		return TypeMetaReference(TypeMeta::String, TypeMeta::string_declaration, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Addition, TypeMeta::String, TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int> : ITypeCheckDispatcher{															
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		StringLiteralNode *l1 = t1.string_literal, *result = 0x0;
		IntegerLiteralNode *l2 = t2.integer_literal;
		if (l1 && l2) {
			std::stringstream ss;
			ss << l1->value << l2->value;

			result = new StringLiteralNode();
			std::getline(ss, result->value);
		}
		return TypeMetaReference(TypeMeta::String, TypeMeta::string_declaration, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Addition, TypeMeta::String, TypeMeta::Char> : ITypeCheckDispatcher{																						
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		StringLiteralNode *l1 = t1.string_literal, *result = 0x0;
		CharacterLiteralNode *l2 = t2.character_literal;
		if (l1 && l2) {
			std::stringstream ss;
			ss << l1->value << (wchar_t)l2->value;

			result = new StringLiteralNode();
			std::getline(ss, result->value);
		}
		return TypeMetaReference(TypeMeta::String, TypeMeta::string_declaration, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Addition, TypeMeta::String, TypeMeta::Boolean> : ITypeCheckDispatcher{																					
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		StringLiteralNode *l1 = t1.string_literal, *result = 0x0;
		BooleanLiteralNode *l2 = t2.boolean_literal;
		if (l1 && l2) {
			std::stringstream ss;
			ss << l1->value << (l2->value ? "true" : "false");

			result = new StringLiteralNode();
			std::getline(ss, result->value);
		}
		return TypeMetaReference(TypeMeta::String, TypeMeta::string_declaration, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Addition, TypeMeta::String, TypeMeta::Null> : ITypeCheckDispatcher{																						
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &) {
		StringLiteralNode *l1 = t1.string_literal, *result = 0x0;
		if (l1) {
			std::stringstream ss;
			ss << l1->value << "null";

			result = new StringLiteralNode();
			std::getline(ss, result->value);
		}
		return TypeMetaReference(TypeMeta::String, TypeMeta::string_declaration, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Addition, TypeMeta::String, TypeMeta::Reference | TypeMeta::Array> : ITypeCheckDispatcher{
	TypeMetaReference op(const TypeMetaReference &, const TypeMetaReference &) {
		return TypeMetaReference(TypeMeta::String, TypeMeta::string_declaration, 0x0);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Addition, TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int, TypeMeta::String> : ITypeCheckDispatcher{															
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		StringLiteralNode *l1 = t2.string_literal, *result = 0x0;
		IntegerLiteralNode *l2 = t1.integer_literal;
		if (l1 && l2) {
			std::stringstream ss;
			ss << l2->value << l1->value;

			result = new StringLiteralNode();
			std::getline(ss, result->value);
		}
		return TypeMetaReference(TypeMeta::String, TypeMeta::string_declaration, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Addition, TypeMeta::Char, TypeMeta::String> : ITypeCheckDispatcher{																						
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		StringLiteralNode *l1 = t2.string_literal, *result = 0x0;
		CharacterLiteralNode *l2 = t1.character_literal;
		if (l1 && l2) {
			std::stringstream ss;
			ss << (wchar_t)l2->value << l1->value;

			result = new StringLiteralNode();
			std::getline(ss, result->value);
		}
		return TypeMetaReference(TypeMeta::String, TypeMeta::string_declaration, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Addition, TypeMeta::Boolean, TypeMeta::String> : ITypeCheckDispatcher{																					
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		StringLiteralNode *l1 = t2.string_literal, *result = 0x0;
		BooleanLiteralNode *l2 = t1.boolean_literal;
		if (l1 && l2) {
			std::stringstream ss;
			ss << (l2->value ? "true" : "false") << l1->value;

			result = new StringLiteralNode();
			std::getline(ss, result->value);
		}
		return TypeMetaReference(TypeMeta::String, TypeMeta::string_declaration, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Addition, TypeMeta::Null, TypeMeta::String> : ITypeCheckDispatcher{																						
	TypeMetaReference op(const TypeMetaReference &, const TypeMetaReference &t2) {
		StringLiteralNode *l1 = t2.string_literal, *result = 0x0;
		if (l1) {
			std::stringstream ss;
			ss << "null" << l1->value;

			result = new StringLiteralNode();
			std::getline(ss, result->value);
		}
		return TypeMetaReference(TypeMeta::String, TypeMeta::string_declaration, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Addition, TypeMeta::Reference, TypeMeta::String> : ITypeCheckDispatcher{
	TypeMetaReference op(const TypeMetaReference &, const TypeMetaReference &) {
		return TypeMetaReference(TypeMeta::String, TypeMeta::string_declaration, 0x0);
	}
};
/* ADDITIVE */
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Addition, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int> : ITypeCheckDispatcher{			
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		IntegerLiteralNode *l1 = t1.integer_literal, *l2 = t2.integer_literal, *result = 0x0;
		if (l1 && l2) {
			result = new IntegerLiteralNode();
			//Because of range checking, literal nodes hold 64bit integers. So, do the addition in a temp
			int result_value = (int)(l1->value + l2->value);
			result->value = result_value;
		}
		return TypeMetaReference(TypeMeta::Int, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Subtraction, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int> : ITypeCheckDispatcher{		
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		IntegerLiteralNode *l1 = t1.integer_literal, *l2 = t2.integer_literal, *result = 0x0;
		if (l1 && l2) {
			result = new IntegerLiteralNode();
			//Because of range checking, literal nodes hold 64bit integers. So, use a temp
			int result_value = (int)(l1->value - l2->value);
			result->value = result_value;
		}
		return TypeMetaReference(TypeMeta::Int, 0x0, result);
	}
};
/* MULTIPLICATIVE */
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Multiplication, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int> : ITypeCheckDispatcher{	
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		IntegerLiteralNode *l1 = t1.integer_literal, *l2 = t2.integer_literal, *result = 0x0;
		if (l1 && l2) {
			result = new IntegerLiteralNode();
			//Because of range checking, literal nodes hold 64bit integers. So, use a temp
			int result_value = (int)(l1->value * l2->value);
			result->value = result_value;
		}
		return TypeMetaReference(TypeMeta::Int, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Division, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int> : ITypeCheckDispatcher{			
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		IntegerLiteralNode *l1 = t1.integer_literal, *l2 = t2.integer_literal, *result = 0x0;
		if (l1 && l2 && l2->value != 0) {
			result = new IntegerLiteralNode();
			//Because of range checking, literal nodes hold 64bit integers. So, use a temp
			int result_value = (int)(l1->value / l2->value);
			result->value = result_value;
		}
		return TypeMetaReference(TypeMeta::Int, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Modulus, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int> : ITypeCheckDispatcher{			
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		IntegerLiteralNode *l1 = t1.integer_literal, *l2 = t2.integer_literal, *result = 0x0;
		if (l1 && l2) {
			result = new IntegerLiteralNode();
			//Because of range checking, literal nodes hold 64bit integers. So, use a temp
			int result_value = (int)(l1->value % l2->value);
			result->value = result_value;
		}
		return TypeMetaReference(TypeMeta::Int, 0x0, result);
	}
};
/* NEGATION */
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Negation, TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int | TypeMeta::Char, TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int | TypeMeta::Char> : ITypeCheckDispatcher{			
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &) {
		IntegerLiteralNode *i = t1.integer_literal;
		IntegerLiteralNode *result = 0x0;
		if (i) {
			result = new IntegerLiteralNode();
			result->value = i->value * -1;
		}
		return TypeMetaReference(TypeMeta::Int, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Complement, TypeMeta::Boolean, TypeMeta::Boolean> : ITypeCheckDispatcher{
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &) {
		BooleanLiteralNode *b = t1.boolean_literal;
		BooleanLiteralNode *result = 0x0;
		if (b) {
			result = new BooleanLiteralNode();
			result->value = !b->value;
		}
		return TypeMetaReference(TypeMeta::Boolean, 0x0, result);
	}
};


/* CASTING */
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Casting, TypeMeta::Int, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int> : ITypeCheckDispatcher{													
	TypeMetaReference op(const TypeMetaReference &, const TypeMetaReference &t2) {
		return TypeMetaReference(TypeMeta::Int, 0x0, t2.reference);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Casting, TypeMeta::Short, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int> : ITypeCheckDispatcher{													
	TypeMetaReference op(const TypeMetaReference &, const TypeMetaReference &t2) {
		IntegerLiteralNode *n = t2.integer_literal, *result = 0x0;
		if (n) {
			result = new IntegerLiteralNode();
			result->value = (int16_t)n->value;
		}
		return TypeMetaReference(TypeMeta::Short, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Casting, TypeMeta::Byte, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int> : ITypeCheckDispatcher{													
	TypeMetaReference op(const TypeMetaReference &, const TypeMetaReference &t2) {
		IntegerLiteralNode *n = t2.integer_literal, *result = 0x0;
		if (n) {
			result = new IntegerLiteralNode();
			result->value = (int8_t)n->value;
		}
		return TypeMetaReference(TypeMeta::Byte, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Casting, TypeMeta::Char, TypeMeta::Char | TypeMeta::Byte | TypeMeta::Short | TypeMeta::Int> : ITypeCheckDispatcher{													
	TypeMetaReference op(const TypeMetaReference &, const TypeMetaReference &t2) {
		CharacterLiteralNode *n = t2.character_literal, *result = 0x0;
		if (n) {
			result = new CharacterLiteralNode();
			result->value = (uint16_t)n->value;
		}
		return TypeMetaReference(TypeMeta::Char, 0x0, result);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Casting, TypeMeta::Boolean, TypeMeta::Boolean> : ITypeCheckDispatcher{																					
	TypeMetaReference op(const TypeMetaReference &, const TypeMetaReference &t2) {
		return TypeMetaReference(TypeMeta::Boolean, 0x0, t2.boolean_literal);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Casting, TypeMeta::Reference | TypeMeta::String, TypeMeta::Reference | TypeMeta::String> : ITypeCheckDispatcher{													
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		ClassDeclarationNode *T = t1.reference, *S = t2.reference;
		bool S_is_class = S->Type() == "ClassDeclaration", T_is_class = T->Type() == "ClassDeclaration";

		if (S_is_class) {
			
			if (T_is_class) {
				//S has to be a subclass of T, or T a subclass of S
				if (S != T && !IsSubclassOf(S, T) && !IsSubclassOf(T, S)) return TypeMetaReference();

			//T is an inferface, and, if it's final, then S must implement the interface directly
			} else if (S->modifier & ModifiedNode::Final) {
				for (std::set<InterfaceDeclarationNode*>::iterator it = S->interfaces.begin(); it != S->interfaces.end(); ++it) {
					if (*it == T) return t1;
				}
				return TypeMetaReference();
			}
		} else {
			if (T_is_class) {
				if (!(T->modifier & ModifiedNode::Final)) return t1;
			} else {
				//If both are Interfaces, they can cast iff they don't contain methods with the same signature and different return types
				for (std::set<TypedNode*, compare_decls>::iterator S_it = S->elements_in_scope[DeclarationNode::Method].begin(); S_it != S->elements_in_scope[DeclarationNode::Method].end(); ++S_it) {
					for (std::set<TypedNode*, compare_decls>::iterator T_it = T->elements_in_scope[DeclarationNode::Method].begin(); T_it != T->elements_in_scope[DeclarationNode::Method].end(); ++T_it) {
						MethodDeclarationNode *S_m = (MethodDeclarationNode*)*S_it, *T_m = (MethodDeclarationNode*)*T_it;
						if (S_m->identifier == T_m->identifier && S_m->type_meta.reference != T_m->type_meta.reference) {
							return TypeMetaReference();
						}
					}
				}

			}
		}
		return t1;
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Casting, TypeMeta::Array, TypeMeta::Reference | TypeMeta::String> : ITypeCheckDispatcher{																	
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		ClassDeclarationNode *T = t1.reference, *S = t2.reference;
		bool S_is_class = S->Type() == "ClassDeclaration";

		if (S_is_class) {
			if (S->package_name + S->identifier != "java.lang.Object") return TypeMetaReference();
			else return t1;
		} else {
			//if (!(t1.category & TypeMeta::Reference)) return TypeMetaReference();

			//for (std::set<InterfaceDeclarationNode*>::iterator it = T->interfaces.begin(); it != T->interfaces.end(); ++it) {
			//	if (*it == S) return t1;
			//}
			return TypeMetaReference();
		}
		
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Casting, TypeMeta::Reference | TypeMeta::String, TypeMeta::Array> : ITypeCheckDispatcher{																	
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &) {
		ClassDeclarationNode *T = t1.reference;
		bool T_is_class = T->Type() == "ClassDeclaration";

		//If it is a class, then it has to be Object type
		if (T_is_class) {
			if (T->package_name + T->identifier != "java.lang.Object") return TypeMetaReference();

		//If it is an interface, then it has to be Serializable or Cloneable
		} else {
			if (T->package_name != "java.io." || (T->identifier != "Serializable" && T->identifier != "Cloneable")) return TypeMetaReference();
		}

		return t1;
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Casting, TypeMeta::Array, TypeMeta::Array> : ITypeCheckDispatcher{																						
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		bool r1 = (t1.category & TypeMeta::Reference || t1.category & TypeMeta::String) != 0x0, r2 = (t2.category & TypeMeta::Reference || t2.category & TypeMeta::String) != 0x0;
		//If both are primitives, they have to match
		if (!r1 && !r2) {
			if(t1.category == t2.category) return t1;
			else return TypeMetaReference();
		} else if (r1 && r2) {
			// Two references can be cast to each other iff their component types cast
			return TypeCheckDispatcher<ITypeCheckDispatcher::Casting, TypeMeta::Reference | TypeMeta::String, TypeMeta::Reference | TypeMeta::String>().op(t1, t2);
		} else return TypeMetaReference();
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Casting, TypeMeta::Array | TypeMeta::Reference | TypeMeta::String, TypeMeta::Null> : ITypeCheckDispatcher{
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &) {
		return t1;
	}
};

/* INSTANCEOF */
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::InstanceOf, TypeMeta::Reference | TypeMeta::String, TypeMeta::Reference | TypeMeta::String> : ITypeCheckDispatcher{
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		if (TypeCheckDispatcher<ITypeCheckDispatcher::Casting, TypeMeta::Reference | TypeMeta::String, TypeMeta::Reference | TypeMeta::String>().op(t1, t2).category != TypeMeta::Error) return TypeMetaReference(TypeMeta::Boolean, 0x0, 0x0);
		else return TypeMetaReference();
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::InstanceOf, TypeMeta::Array, TypeMeta::Reference> : ITypeCheckDispatcher{
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		if (TypeCheckDispatcher<ITypeCheckDispatcher::Casting, TypeMeta::Array, TypeMeta::Reference | TypeMeta::String>().op(t1, t2).category != TypeMeta::Error) return TypeMetaReference(TypeMeta::Boolean, 0x0, 0x0);
		else return TypeMetaReference();
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::InstanceOf, TypeMeta::Reference, TypeMeta::Array> : ITypeCheckDispatcher{
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		if (TypeCheckDispatcher<ITypeCheckDispatcher::Casting, TypeMeta::Reference | TypeMeta::String, TypeMeta::Array>().op(t1, t2).category != TypeMeta::Error) return TypeMetaReference(TypeMeta::Boolean, 0x0, 0x0);
		else return TypeMetaReference();
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::InstanceOf, TypeMeta::Array, TypeMeta::Array> : ITypeCheckDispatcher{
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		if (TypeCheckDispatcher<ITypeCheckDispatcher::Casting, TypeMeta::Array, TypeMeta::Array>().op(t1, t2).category != TypeMeta::Error) return TypeMetaReference(TypeMeta::Boolean, 0x0, 0x0);
		else return TypeMetaReference();
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::InstanceOf, TypeMeta::Null, TypeMeta::Reference | TypeMeta::String> : ITypeCheckDispatcher{
	TypeMetaReference op(const TypeMetaReference &, const TypeMetaReference &) {
		BooleanLiteralNode *result = new BooleanLiteralNode();
		result->value = false;
		return TypeMetaReference(TypeMeta::Boolean, 0x0, result);
	}
};

/* ASSIGNMENT */
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Assignment, TypeMeta::Int , TypeMeta::Int> : ITypeCheckDispatcher{																						
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &) {
		return t1;
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Assignment, TypeMeta::Int | TypeMeta::Short, TypeMeta::Short> : ITypeCheckDispatcher{																		
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &) {
		return t1;
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Assignment, TypeMeta::Int | TypeMeta::Short | TypeMeta::Byte, TypeMeta::Byte> : ITypeCheckDispatcher{															
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &) {
		return t1;
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Assignment, TypeMeta::Int | TypeMeta::Char, TypeMeta::Char> : ITypeCheckDispatcher{																			
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &) {
		return t1;
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Assignment, TypeMeta::Boolean, TypeMeta::Boolean> : ITypeCheckDispatcher{																				
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &) {
		return t1;
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Assignment, TypeMeta::Reference | TypeMeta::String, TypeMeta::Reference | TypeMeta::String> : ITypeCheckDispatcher{												
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		ClassDeclarationNode *T = t1.reference, *S = t2.reference;

		if (S == T) return t1;

		bool S_is_class = S->Type() == "ClassDeclaration", T_is_class = T->Type() == "ClassDeclaration";

		//In any case that T is an interface, S must implement it
		if (!T_is_class) {
			for (ClassDeclarationNode *c = S; c; c = c->super) {
				for (std::set<InterfaceDeclarationNode*>::iterator it = c->interfaces.begin(); it != c->interfaces.end(); ++it) {
					if (*it == T) return t1;
				}
			}
			return TypeMetaReference();

		//S and T are both classes
		} else if (S_is_class) {
			//S must be a subclass of T
			if (IsSubclassOf(S, T)) return t1;
			//This is narrowing or doesn't exist
			else return TypeMetaReference();
		//S is an interface and T is a class
		} else {
			//T must be the Object type
			if (T->package_name + T->identifier == "java.lang.Object") return t1;
			else return TypeMetaReference();
		}
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Assignment, TypeMeta::Reference | TypeMeta::String | TypeMeta::Array, TypeMeta::Null> : ITypeCheckDispatcher{												
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &) {
		return TypeMetaReference(t1.category, t1.reference, 0x0);
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Assignment, TypeMeta::Reference, TypeMeta::Array> : ITypeCheckDispatcher{																			
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &) {
		std::string qualified_type = t1.reference->package_name + t1.reference->identifier;
		
		//An array can widen to only these 3 classes
		if (qualified_type != "java.lang.Object" && qualified_type != "java.lang.Cloneable" && qualified_type != "java.io.Serializable") return TypeMetaReference();
		return t1;
	}
};
template<>
struct TypeCheckDispatcher<ITypeCheckDispatcher::Assignment, TypeMeta::Array, TypeMeta::Array> : ITypeCheckDispatcher{																				
	TypeMetaReference op(const TypeMetaReference &t1, const TypeMetaReference &t2) {
		bool r1 = (t1.category & TypeMeta::Reference || t1.category & TypeMeta::String) != 0x0, r2 = (t2.category & TypeMeta::Reference || t2.category & TypeMeta::String) != 0x0;
		
		//Both component types must be references, and those types must be assignable to each other
		if (r1 && r2) {
			TypeMetaReference t = TypeCheckDispatcher<ITypeCheckDispatcher::Assignment, TypeMeta::Reference | TypeMeta::String, TypeMeta::Reference | TypeMeta::String>().op(t1, t2);
			if (t.category != TypeMeta::Error) t.category = (TYPE)(t.category | TypeMeta::Array);
			return t;
		} else if (!r1 && !r2 && t1.category == t2.category) {
			return t1;
		} else return TypeMetaReference();
	}

};

struct operation_identifier {
	operation_identifier() {}
	operation_identifier(OP o, int t1, int t2) : operation(o), type1(t1), type2(t2) {}
	OP operation;
	int type1, type2;


	bool operator()(const operation_identifier &o1, const operation_identifier &o2){

		return Hash(o1) < Hash(o2);
	}
private: 
	bool coincide(const int &t1, const int &t2) {
		//Check if they're both arrays or both not
		if ((t1 & TypeMeta::Array) != (t2 & TypeMeta::Array)) return false;

		//Get the intersection
		unsigned int t = t1 & t2;
		
		//If the infersection is equal to one of the values, that value is a subset of the other
		if (t == t1 || t == t2) return true;
		else return false;
	}

	unsigned int Hash(const operation_identifier &o) {
		unsigned int h = o.operation << 23;
		
		if (o.type1 & TypeMeta::Array) h |= TypeMeta::Array << 11;
		else h |= o.type1 << 11;

		if (o.type2 & TypeMeta::Array) h |= TypeMeta::Array;
		else h |= o.type2;

		return h;
	}
};
#endif
